#include "../flow/arrowflow.h"
#include "../flow/flowtext.h"
#include "../flow/noteflow.h"
#include "../shape/action.h"
#include "../shape/decision.h"
#include "../shape/finalnode.h"
#include "../shape/initialnode.h"
#include "../shape/note.h"
#include "../flow/tempshape.h"

#include "load_xml.h"
#include "save_xml.h" // FileErrorException is here
#include <QtXml/QDomNode>
#include <QtXml/QDomDocument>
#include <QFile>
#include <QHash>
#include <QDebug>

typedef int ID;
typedef Shape *(*LOADFUNC) (QDomNode &r, QHash<ID, Shape *> &ids);

Shape *load_TempShape (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	double x = as.namedItem("x").toAttr().value().toDouble();
	double y = as.namedItem("y").toAttr().value().toDouble();
	TempShape *s = new TempShape(QPointF(x,y));
	ids[id] = s;
	return s;
}

void loadFlowKnots (Flow *flow, const QDomNamedNodeMap &as)
{
	int count = as.namedItem("KnotsCount").toAttr().value().toInt();
	QList<QPointF> list;
	for(int i=0; i<count; ++i)
	{
		QPointF pt = QPointF(
			as.namedItem("KnotX_"+QString::number(i)).toAttr().value().toDouble(),
			as.namedItem("KnotY_"+QString::number(i)).toAttr().value().toDouble()
		);
		list << pt;
	}
	flow->setFlowKnots(list);
}

Shape *load_ArrowFlow (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	ID sid = as.namedItem("fromID").toAttr().value().toInt();
	ID eid = as.namedItem("toID").toAttr().value().toInt();
	double deltaX = as.namedItem("offsetX").toAttr().value().toDouble();
	double deltaY = as.namedItem("offsetY").toAttr().value().toDouble();
	QString text = as.namedItem("text").toAttr().value();
	if (!ids[sid] || !ids[eid])
		throw "can't load this";
	ArrowFlow *flow = new ArrowFlow(ids[sid], ids[eid]);
	ids[id] = flow;
	flow->setText(text);
	flow->textShape()->setOffset(deltaX, deltaY);
	loadFlowKnots(flow, as);
	return flow;
}

Shape *load_NoteFlow (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	ID sid = as.namedItem("fromID").toAttr().value().toInt();
	ID eid = as.namedItem("toID").toAttr().value().toInt();
	if (!ids[sid] || !ids[eid])
		throw "can't load this";
	NoteFlow *flow = new NoteFlow(ids[sid], ids[eid]);
	ids[id] = flow;
	loadFlowKnots(flow, as);
	return flow;
}

Shape *load_ActionShape (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	double x = as.namedItem("x").toAttr().value().toDouble();
	double y = as.namedItem("y").toAttr().value().toDouble();
	double width = as.namedItem("width").toAttr().value().toDouble();
	double height = as.namedItem("height").toAttr().value().toDouble();
	QString title = as.namedItem("title").toAttr().value();
	QString description = as.namedItem("description").toAttr().value();
	ActionShape *s = new ActionShape(QPointF(x,y));
	s->setSize(QSizeF(width, height));
	s->setTitle(title);
	s->setDescription(description);
	ids[id] = s;
	return s;
}

Shape *load_NoteShape (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	double x = as.namedItem("x").toAttr().value().toDouble();
	double y = as.namedItem("y").toAttr().value().toDouble();
	double width = as.namedItem("width").toAttr().value().toDouble();
	double height = as.namedItem("height").toAttr().value().toDouble();
	QString title = as.namedItem("title").toAttr().value();
	QString description = as.namedItem("description").toAttr().value();
	NoteShape *s = new NoteShape(QPointF(x,y));
	s->setSize(QSizeF(width, height));
	s->setTitle(title);
	s->setDescription(description);
	ids[id] = s;
	return s;
}

Shape *load_DecisionShape (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	double x = as.namedItem("x").toAttr().value().toDouble();
	double y = as.namedItem("y").toAttr().value().toDouble();
	double width = as.namedItem("width").toAttr().value().toDouble();
	double height = as.namedItem("height").toAttr().value().toDouble();
	bool corners = as.namedItem("corners").toAttr().value()=="true";
	DecisionShape *s = new DecisionShape(QPointF(x,y));
	s->setSize(QSizeF(width, height));
	s->setConnectToCorners(corners);
	ids[id] = s;
	return s;
}

Shape *load_InitialNodeShape (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	double x = as.namedItem("x").toAttr().value().toDouble();
	double y = as.namedItem("y").toAttr().value().toDouble();
	InitialNodeShape *s = new InitialNodeShape(QPointF(x,y));
	ids[id] = s;
	return s;
}

Shape *load_FinalNodeShape (QDomNode &r, QHash<ID, Shape *> &ids)
{
	const QDomNamedNodeMap &as = r.attributes();
	ID id = as.namedItem("ID").toAttr().value().toInt();
	double x = as.namedItem("x").toAttr().value().toDouble();
	double y = as.namedItem("y").toAttr().value().toDouble();
	FinalNodeShape *s = new FinalNodeShape(QPointF(x,y));
	ids[id] = s;
	return s;
}

void file_load_xml (const QString &filename, CustomGraphicsScene *scene, bool *isdraft, QGraphicsView *view)
{
	QFile file(filename);
	if (!file.open(QIODevice::ReadOnly))
		throw FileErrorException(filename);
	QByteArray data = file.readAll();
	try
	{
		load_scene_xml(QString(data), scene, isdraft, view);
	}
	catch (...)
	{
		throw FileErrorException("");
	}
	file.close();
}

void load_scene_xml (const QString &xml, CustomGraphicsScene *scene, bool *isdraft, QGraphicsView *view)
{
	QDomDocument doc;
	if (!doc.setContent(xml))
		throw FileErrorException("");

	QHash<ID, Shape *> ids;
	QHash<QString, LOADFUNC> loadfuncs;
	loadfuncs["ArrowFlow"] = load_ArrowFlow;
	loadfuncs["NoteFlow"] = load_NoteFlow;
	loadfuncs["ActionShape"] = load_ActionShape;
	loadfuncs["InitialNodeShape"] = load_InitialNodeShape;
	loadfuncs["FinalNodeShape"] = load_FinalNodeShape;
	loadfuncs["DecisionShape"] = load_DecisionShape;
	loadfuncs["NoteShape"] = load_NoteShape;
	loadfuncs["TempShape"] = load_TempShape;

	if (isdraft) *isdraft = doc.documentElement().attribute("draft", "0")=="1";
	if (view)
	{
		double x = doc.documentElement().attribute("viewx", "0").toDouble();
		double y = doc.documentElement().attribute("viewy", "0").toDouble();
		view->centerOn(x, y);
	}

	const QDomNodeList &items = doc.documentElement().childNodes();
	QList<QString> shapes = (QList<QString>()
		<< "ActionShape"
		<< "NoteShape"
		<< "InitialNodeShape"
		<< "FinalNodeShape"
		<< "DecisionShape"
		<< "TempShape"
	);
	QList<QString> flows = (QList<QString>() << "ArrowFlow");

	QList<int> first, second, other;
	for(int i=0; i<items.count(); ++i)
	{
		QDomElement e = items.at(i).toElement();
		if (shapes.contains(e.tagName()))
			first.append(i);
		else if (flows.contains(e.tagName()))
			second.append(i);
		else
			other.append(i);
	}

	QList<int> all = (QList<int>() << first << second << other);
	for(int i=0; i<items.count(); ++i)
	{
		QDomElement e = items.at(all[i]).toElement();
		if (loadfuncs[e.tagName()])
		{
			Shape *shp = loadfuncs[e.tagName()](e, ids);
			scene->addShape(shp);
		}
	}

}
